package base.java8;

import com.alibaba.fastjson2.JSON;
import lombok.AllArgsConstructor;
import lombok.Data;

import java.time.LocalDate;
import java.util.*;
import java.util.function.*;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/**
 * *****************************************************************************************************************************************
 * 1. 流的创建
 * <p>
 * * {@link Stream#of(Object)}                                 返回单个元素的流
 * * {@link Stream#of(Object[])}                               返回多个元素的流
 * * {@link Stream#ofNullable(Object)}                         预防流的空指针异常
 * * {@link Stream#empty()}                                    返回一个空流
 * * {@link Stream#iterate(Object, UnaryOperator)}             传入一个初始值,传入一个转换规则,无线创建,需要搭配limit(慎用)
 * * {@link Stream#iterate(Object, Predicate, UnaryOperator)}  传入一个初始值,传入一个停止断言,传入一个生产规则,循环创建
 * * {@link Stream#generate(Supplier)}                         传入一个生产者,无限创建,为了限制流中元素的数量,需要搭配limit(慎用)
 * * {@link Stream#concat(Stream, Stream)}                     拼接两个流,生成一个新流
 * * {@link Collection#stream()}                               集合接口的默认方法,包括继承Collection的接口,如：Set,List,Map,SortedSet等等
 * * {@link Arrays#stream(Object[])}                           通过Arrays的静态方法,传入一个泛型数组,创建一个流
 * *****************************************************************************************************************************************
 * 2. 流的中间操作 <这些操作用于管道化其他方法并转换为其他流,它们不会产生结果,因为这些操作在终端操作执行之前不会调用>
 * <p>
 * * {@link Stream#filter(Predicate)}             过滤
 * * {@link Stream#map(Function)}                 转换
 * * {@link Stream#mapMulti(BiConsumer)}          多重转换
 * * {@link Stream#mapToInt(ToIntFunction)}       转换
 * * {@link Stream#mapMultiToInt(BiConsumer)}}    多重转换
 * * {@link Stream#mapToLong(ToLongFunction)}     转换
 * * {@link Stream#mapMultiToLong(BiConsumer)}    多重转换
 * * {@link Stream#mapToDouble(ToDoubleFunction)} 转换
 * * {@link Stream#mapMultiToDouble(BiConsumer)}  多重转换
 * * {@link Stream#flatMap(Function)}             平铺摊开
 * * {@link Stream#flatMapToInt(Function)}        平铺摊开
 * * {@link Stream#flatMapToLong(Function)}       平铺摊开
 * * {@link Stream#flatMapToDouble(Function)}     平铺摊开
 * * {@link Stream#distinct()}                    去重
 * * {@link Stream#sorted()}                      排序 默认升序
 * * {@link Stream#sorted(Comparator)}            按传入的条件排序
 * * {@link Stream#peek(Consumer)}                流不变 接收一个没有返回值的λ表达式,可以做一些输出,外部处理
 * * {@link Stream#limit(long)}                   保留前n个元素
 * * {@link Stream#skip(long)}                    跳过前n个元素 (对于limit 和 skip 配合使用可以实现分页操作)
 * * {@link Stream#takeWhile(Predicate)}          返回匹配之前的所有
 * * {@link Stream#dropWhile(Predicate)}          返回匹配之后的所有
 * <p>
 * *****************************************************************************************************************************************
 * 3. 流的终止操作 <直接产生结果>
 * <p>
 * * {@link Stream#forEach(Consumer)}          循环
 * * {@link Stream#forEachOrdered(Consumer)}   并行循环
 * * {@link Stream#toArray()}                  转成数组
 * * {@link Stream#toArray(IntFunction)}       转成数组
 * * {@link Stream#min(Comparator)}            获取最小值
 * * {@link Stream#max(Comparator)}            获取最大值
 * * {@link Stream#count()}                    获取总数
 * * {@link Stream#allMatch(Predicate)}        任意元素匹配时返回true
 * * {@link Stream#allMatch(Predicate)}        所有元素匹配时返回true
 * * {@link Stream#noneMatch(Predicate)}       没有元素匹配时返回true
 * * {@link Stream#findFirst()}                返回第一个元素
 * * {@link Stream#findAny()}                  返回任意元素
 * * {@link Stream#reduce(BinaryOperator)}                      reduce(返回值与输入类型一致)
 * * {@link Stream#reduce(Object, BinaryOperator)}              reduce,需要传入初始值(返回值与输入类型一致)
 * * {@link Stream#reduce(Object, BiFunction, BinaryOperator)}  reduce有三个参数时,第三个参数combiner主要是使用在并行计算的场景下,如果Stream是非并行时,第三个参数实际上是不生效的
 * * {@link Stream#collect(Supplier, BiConsumer, BiConsumer)}   收集器操作
 * * {@link Stream#collect(Collector)}                          收集器操作(参考4)
 * * {@link Stream#toList()}                                    java16,直接转换成list
 * <p>
 * *****************************************************************************************************************************************
 * 4. Collectors(作为生产具体Collector的工具类)
 * <p>
 * * {@link Collectors#toCollection(Supplier)}将流中的元素映射到集合中,这里使用Collection,泛指多种集合。
 * * {@link Collectors#toList()}`将流中的元素映射到List中
 * * {@link Collectors#toSet()}将流中的元素映射到Set中
 * * {@link Collectors#toUnmodifiableList()}将流中的元素映射到不可更改的List中
 * * {@link Collectors#toUnmodifiableSet()}将流中的元素映射到不可更改的Set中
 * * {@link Collectors#toMap(Function, Function)}将流中的元素映射到Map,key相同会报错
 * * {@link Collectors#toMap(Function, Function, BinaryOperator)}将流中的元素映射到Map,key相同时对值进行合并操作
 * * {@link Collectors#toMap(Function, Function, BinaryOperator, Supplier)}将流中的元素映射到Map,key相同时对值进行合并操作,可以自定义返回的Map类型
 * * {@link Collectors#toUnmodifiableMap(Function, Function)}将流中的元素映射到可更改的Map,key相同会报错
 * * {@link Collectors#toUnmodifiableMap(Function, Function, BinaryOperator)}将流中的元素映射到可更改的Map,key相同时对值进行合并操作
 * * {@link Collectors#toConcurrentMap(Function, Function)}将流中的元素映射到ConcurrentMap,key相同会报错
 * * {@link Collectors#toConcurrentMap(Function, Function, BinaryOperator)}将流中的元素映射到ConcurrentMap,key相同时对值进行合并操作
 * * {@link Collectors#toConcurrentMap(Function, Function, BinaryOperator, Supplier)}将流中的元素映射到ConcurrentMap,key相同时对值进行合并操作,可以自定义返回的Map类型
 * *
 * * {@link Collectors#joining()}                                         以空字符串拼接所有元素
 * * {@link Collectors#joining(CharSequence)}                             接受一个参数字符串作为拼接符,拼接所有元素
 * * {@link Collectors#joining(CharSequence, CharSequence, CharSequence)} 接受一个参数字符串作为拼接符,拼接所有元素,并在拼接完成后添加传递的前缀和后缀
 * * {@link Collectors#counting()}                          计数
 * * {@link Collectors#minBy(Comparator)}                   求最小
 * * {@link Collectors#maxBy(Comparator)}                   求最大
 * * {@link Collectors#summingInt(ToIntFunction)}           求和
 * * {@link Collectors#summingLong(ToLongFunction)}         求和
 * * {@link Collectors#summingDouble(ToDoubleFunction)}     求和
 * * {@link Collectors#averagingInt(ToIntFunction)}         求平均值
 * * {@link Collectors#averagingLong(ToLongFunction)}       求平均值
 * * {@link Collectors#averagingDouble(ToDoubleFunction)}   求平均值
 * * {@link Collectors#summarizingInt(ToIntFunction)}       归总
 * * {@link Collectors#summarizingLong(ToLongFunction)}     归总
 * * {@link Collectors#summarizingDouble(ToDoubleFunction)} 归总
 * *
 * * {@link Collectors#reducing(BinaryOperator)}                    用一个func对流中的元素做统计归纳
 * * {@link Collectors#reducing(Object, BinaryOperator)}            用一个func对流中的元素做统计归纳,并合并初始值a
 * * {@link Collectors#reducing(Object, Function, BinaryOperator)}  第三个参数combiner主要是使用在并行计算的场景下,如果Stream是非并行时,第三个参数实际上是不生效的
 * *
 * * {@link Collectors#mapping(Function, Collector)}             将Function应用于输入元素,然后将它们累积到给定的Collector
 * * {@link Collectors#flatMapping(Function, Collector)}         将Stream_Function应用于输入元素,然后将它们累积到给定的Collector
 * * {@link Collectors#filtering(Predicate, Collector)}          将Predicate应用于输入元素,然后将它们累积到给定的Collector(分组/分区之后根据条件过滤)
 * * {@link Collectors#collectingAndThen(Collector, Function)}   对结果集收集的结果再用function函数进行处理
 * * {@link Collectors#teeing(Collector, Collector, BiFunction)} 是将downstream1和downstream2的流入合并,然后从merge流出
 * * {@link Collectors#partitioningBy(Predicate)}                根据条件对数据进行分区
 * * {@link Collectors#partitioningBy(Predicate, Collector)}     根据条件对数据进行分区,对分区后的数据再用Collector处理
 * * {@link Collectors#groupingBy(Function)}                                分组
 * * {@link Collectors#groupingBy(Function, Collector)}                     分组,对分组后的数据再用Collector处理
 * * {@link Collectors#groupingBy(Function, Supplier, Collector)}           分组,对分组后的数据再用Collector处理,可以自定义返回Map类型
 * * {@link Collectors#groupingByConcurrent(Function)}                      分组
 * * {@link Collectors#groupingByConcurrent(Function, Collector)}           分组,对分组后的数据再用Collector处理
 * * {@link Collectors#groupingByConcurrent(Function, Supplier, Collector)} 分组,对分组后的数据再用Collector处理,可以自定义返回ConcurrentMap类型
 * <p>
 *
 * @author 王霄
 * @date 2021/9/10 18:09
 */
public class StreamDemo {
    public static void main(String[] args) {


        //###################################流的创建##################################################
        //练习1：打印一个有可能为空的集合中的每个元素，为空时不打印

        //准备：先生成一个有可能为空的集合
        var maybeNull = new Random().nextInt(2) == 1 ? null : List.of("1", "2", "3", "4");

        //方法1
        if (maybeNull != null) {
            maybeNull.forEach(System.out::println);
        }
        //方法2
        Optional.ofNullable(maybeNull).orElse(new ArrayList<>()).forEach(System.out::println);
        //方法3
        Stream.ofNullable(maybeNull).flatMap(Collection::stream).forEach(System.out::println);


        //练习2：合并3个list为一个list,并打印
        var list1 = List.of("a", "b", "c");
        var list2 = List.of("d", "e", "f");
        var list3 = List.of("g", "h", "i");

        //方法1
        var concat1 = Stream.concat(Stream.concat(list1.stream(), list2.stream()), list3.stream()).toList();
        System.out.println("concat1 = " + concat1);
        //方法2
        var concat2 = Stream.of(list1, list2, list3).flatMap(Collection::stream).toList();
        System.out.println("concat2 = " + concat2);

        //练习3：从1到10生成每个数的平方

        //方法1
        var s1 = Stream.iterate(1, i -> i + 1)
                .limit(10)
                .map(x -> x * x)
                .toList();
        System.out.println("s1 = " + s1);
        //方法2
        var s2 = Stream.iterate(1, i -> i <= 10, i -> i + 1)
                .map(x -> x * x)
                .toList();
        System.out.println("s2 = " + s2);
        //方法3
        var s3 = IntStream.rangeClosed(1, 10)
                .map(x -> x * x)
                .boxed()
                .toList();
        System.out.println("s3 = " + s3);


        //返回单个元素的流
        var nba = Stream.of("nba");

        //返回多个元素的流
        var nums = Stream.of(2, 3, 5, 7);

        //generate生成
        var generate = Stream.generate(() -> new Random().nextInt(10))
                .limit(10)
                .toList();
        System.out.println("generate = " + generate);


        //多重转换
        var objects = Stream.iterate(1, i -> i <= 10, i -> i + 1)
                .mapMulti((a, b) -> {
                    b.accept(a * a);
                    b.accept(a * a * a);
                })
                .toList();
        System.out.println("objects = " + objects);
        //###################################流的创建##################################################


        var users = User.get();
        var ps = Person.get();


        //自定义排序,按照平方排序排序
        System.out.println(Stream.of(-100, 2, 3, 4, 5, 6, 7).sorted(Comparator.comparing(x -> x * x)).toList());

        //reduce练习
        System.out.println(Stream.of(1, 2, 3, 4, 5, 6, 7).reduce(Integer::sum).orElse(0));
        System.out.println(Stream.of(1, 2, 3, 4, 5, 6, 7).reduce(100, Integer::sum));
        System.out.println(Stream.of(1, 2, 3, 4, 5, 6, 7).reduce("拼接结果：", (a, b) -> a.concat(b.toString()), (a, b) -> "0"));
        System.out.println(Stream.of(1, 2, 3, 4, 5, 6, 7).parallel().reduce("我有", (a, b) -> a.concat(b.toString()), String::concat));

        //join练习
        System.out.println(Stream.of("我", "是", "王", "霄").collect(Collectors.joining("-", "[", "]")));


        //partitioningBy分区练习(collectingAndThen)
        System.out.println(Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9).collect(Collectors.partitioningBy(x -> x > 5)));
        System.out.println(Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9).collect(Collectors.partitioningBy(x -> x > 5, Collectors.counting())));
        System.out.println(Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9).collect(Collectors.partitioningBy(x -> x > 5, Collectors.collectingAndThen(Collectors.maxBy(Comparator.comparingInt(a -> a)), x -> x.orElse(null)))));
        System.out.println(Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9).collect(Collectors.partitioningBy(x -> x > 5, Collectors.summingLong(x -> (long) x))));

        System.out.println(Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9).takeWhile(x -> x < 7).toList());
        System.out.println(Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9).dropWhile(x -> x < 6).toList());


        //统计每个市下面的用户ID(mapping)
        var ids = users.stream()
                .collect(Collectors.groupingBy(User::getArea, Collectors.mapping(User::getId, Collectors.toList())));
        System.out.println(ids);

        //打印出每个人的爱好(flatMapping) 格式 name = 打篮球/学习/看电影
        var hobbys = ps.stream()
                .collect(Collectors.groupingBy(Person::getName,
                        Collectors.flatMapping(x -> x.getHobby().stream(), Collectors.joining("/"))));
        System.out.println(hobbys);


        //合并每个人的爱好(flatMapping)
        //第一种方式
        var mergeHobby = ps.stream()
                .collect(Collectors.toMap(Person::getName, Person::getHobby,
                        (c1, c2) -> Stream.concat(c1.stream(), c2.stream()).toList()));
        System.out.println(mergeHobby);
        //第二种方式
        var mergeHobby2 = ps.stream()
                .collect(Collectors.groupingBy(Person::getName,
                        Collectors.flatMapping(x -> x.getHobby().stream(), Collectors.toList())));
        System.out.println(mergeHobby2);


        //求出每个市注册时间最早的用户ID(collectingAndThen)
        //第一种方式
        var dateMin = users.stream()
                .collect(Collectors.groupingBy(User::getArea, Collectors.minBy(Comparator.comparing(User::getDate))));
        dateMin.forEach((k, v) -> System.out.print(k + "=" + v.map(User::getId).orElse(null)));
        System.out.println();
        //第二种方式
        var dateMin2 = users.stream()
                .collect(Collectors.groupingBy(User::getArea, Collectors.collectingAndThen(Collectors.minBy(Comparator.comparing(User::getDate)), x -> x.map(User::getId).orElse(null))));
        System.out.println(dateMin2);


        //求出最大数和最小数的平方和(teeing)
        var c = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9).collect(Collectors.teeing(
                Collectors.minBy(Comparator.comparing(a -> a)),
                Collectors.maxBy(Comparator.comparing(a -> a)),
                (a, b) -> a.map(x -> x * x).orElse(0) + b.map(x -> x * x).orElse(0)));
        System.out.println(c);


        //假设我们有一个字符串列表,并希望获得与长度大于1的大写字符串相关联的字符串长度的映射,并将它们收集到TreeSet实例中
        var result = Stream.of("aa", "bbb", "c", "dd")
                .collect(Collectors.groupingBy(String::length, Collectors.mapping(String::toUpperCase,
                        Collectors.filtering(s -> s.length() > 1, Collectors.toCollection(TreeSet::new)))));
        var result2 = Stream.of("aa", "bbb", "c", "dd")
                .collect(Collectors.groupingBy(String::length, Collectors.filtering(x -> x.length() > 1,
                        Collectors.mapping(String::toUpperCase, Collectors.toCollection(TreeSet::new)))));
        System.out.println(result);
        System.out.println(result2);


        //消息列表
        List<Msg> msgs = Msg.get();
        //将同一种消息类型的内容合并成List

        //方法一
        var msgMap1 = msgs.stream()
                .collect(Collectors.groupingBy(Msg::type,
                        Collectors.mapping(Msg::data, Collectors.toList())));
        System.out.println(msgMap1);

        //方法一
        var msgMap2 = msgs.stream()
                .collect(Collectors.groupingBy(Msg::type,
                        Collectors.collectingAndThen(Collectors.toList(),
                                x -> x.stream().map(Msg::data).collect(Collectors.toList()))));
        System.out.println(msgMap2);
    }

    public record Msg(String type, Object data) {
        public static List<Msg> get() {
            List<Msg> list = new ArrayList<>();
            list.add(new Msg("A", 1));
            list.add(new Msg("A", "ok"));
            list.add(new Msg("A", 2.3));
            list.add(new Msg("B", 5));
            list.add(new Msg("B", 0Xff));
            list.add(new Msg("B", new byte[0]));
            return list;
        }
    }


    @Data
    @AllArgsConstructor
    public static class Person {

        private String name;
        private List<String> hobby;

        public static List<Person> get() {
            String json = """ 
                    [
                       {
                           "name": "张三",
                           "hobby": [
                               "唱歌",
                               "跳舞"
                           ]
                       },
                       {
                           "name": "张三",
                           "hobby": [
                               "RAP"
                           ]
                       },
                       {
                           "name": "王五",
                           "hobby": [
                               "打篮球",
                               "学习",
                               "看电影"
                           ]
                       }
                    ]
                         """;
            return JSON.parseArray(json, Person.class);
        }
    }


    @Data
    @AllArgsConstructor
    public static class User {
        /**
         * 用户ID
         */
        private String id;
        /**
         * 入职时间
         */
        private LocalDate date;
        /**
         * 地区
         */
        private String area;

        public static List<User> get() {
            List<User> list = new ArrayList<>();
            list.add(new User("01", LocalDate.parse("2021-08-01"), "杭州"));
            list.add(new User("02", LocalDate.parse("2021-08-02"), "杭州"));
            list.add(new User("03", LocalDate.parse("2021-08-03"), "杭州"));
            list.add(new User("04", LocalDate.parse("2021-08-04"), "杭州"));
            list.add(new User("05", LocalDate.parse("2021-08-05"), "杭州"));
            list.add(new User("06", LocalDate.parse("2021-08-01"), "西安"));
            list.add(new User("07", LocalDate.parse("2021-08-02"), "西安"));
            list.add(new User("08", LocalDate.parse("2021-08-03"), "西安"));
            list.add(new User("09", LocalDate.parse("2021-08-04"), "西安"));
            list.add(new User("10", LocalDate.parse("2021-08-05"), "西安"));
            list.add(new User("11", LocalDate.parse("2021-08-01"), "上海"));
            list.add(new User("12", LocalDate.parse("2021-08-02"), "上海"));
            list.add(new User("13", LocalDate.parse("2021-08-03"), "上海"));
            list.add(new User("14", LocalDate.parse("2021-08-04"), "上海"));
            list.add(new User("15", LocalDate.parse("2021-08-05"), "上海"));
            return list;
        }
    }

}
